package net.stuffrepos.bricknetcenter.util.validation;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import net.stuffrepos.util.cache.CacheableMapValue;
import net.stuffrepos.util.cache.CacheableValue;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author Eduardo H. Bogoni <eduardobogoni@gmail.com>
 */
public class ValidationFactory {

    private static final Log log = LogFactory.getLog(ValidationFactory.class);
    private static CacheableMapValue<Class, CacheableMapValue<String, Property>> properties = new CacheableMapValue<Class, CacheableMapValue<String, Property>>() {

        @Override
        protected CacheableMapValue<String, Property> calculate(final Class entityClass) {
            return new CacheableMapValue<String, Property>() {

                @Override
                protected Property calculate(final String propertyName) {
                    return new Property(entityClass, propertyName);
                }
            };
        }
    };
    private static Map<Class, ConstraintBuilder> constraintBuilders = new HashMap<Class, ConstraintBuilder>();

    static {
        constraintBuilders.put(Integer.class, new ConstraintBuilder() {

            @Override
            public Constraint build(Property property) {
                throw new UnsupportedOperationException("NYI");
            }
        });
        constraintBuilders.put(String.class, new ConstraintBuilder() {

            @Override
            public Constraint build(Property property) {
                throw new UnsupportedOperationException("NYI");
            }
        });
        constraintBuilders.put(Date.class, new ConstraintBuilder() {

            @Override
            public Constraint build(Property property) {
                throw new UnsupportedOperationException("NYI");
            }
        });
        constraintBuilders.put(Enum.class, new ConstraintBuilder() {

            @Override
            public Constraint build(Property property) {
                throw new UnsupportedOperationException("NYI");
            }
        });
    }

    public Constraint getFromEntityProperty(Class entityClass, String propertyName) {
        return properties.getValue(entityClass).getValue(propertyName).getConstraint();





    }

    private static interface ConstraintBuilder {

        public Constraint build(Property property);
    }

    private static class Property {

        private final String propertyName;
        private final Class entityClass;
        private static final Log log = LogFactory.getLog(Property.class);
        private final CacheableValue<PropertyDescriptor> propertyDescriptor = new CacheableValue<PropertyDescriptor>() {

            @Override
            protected PropertyDescriptor calculate() {
                try {
                    return (PropertyDescriptor) PropertyUtils.describe(entityInstance.getValue()).get(propertyName);
                } catch (IllegalAccessException ex) {
                    throw new RuntimeException(entityClass + "." + propertyName, ex);
                } catch (InvocationTargetException ex) {
                    throw new RuntimeException(entityClass + "." + propertyName, ex);
                } catch (NoSuchMethodException ex) {
                    throw new RuntimeException(entityClass + "." + propertyName, ex);
                }
            }
        };
        private final CacheableValue<Map<Class<? extends Annotation>, Annotation>> annotations = new CacheableValue<Map<Class<? extends Annotation>, Annotation>>() {

            @Override
            protected Map<Class<? extends Annotation>, Annotation> calculate() {
                List<Annotation> annotations = new LinkedList<Annotation>();
                try {
                    Field field = entityClass.getField(propertyName);
                    annotations.addAll(Arrays.asList(field.getAnnotations()));

                } catch (NoSuchFieldException ex) {
                    log.trace(entityClass + "." + propertyName, ex);
                } catch (SecurityException ex) {
                    log.trace(entityClass + "." + propertyName, ex);
                }

                if (propertyDescriptor.getValue().getReadMethod() != null) {
                    annotations.addAll(Arrays.asList(propertyDescriptor.getValue().getReadMethod().getAnnotations()));
                }
                if (propertyDescriptor.getValue().getWriteMethod() != null) {
                    annotations.addAll(Arrays.asList(propertyDescriptor.getValue().getWriteMethod().getAnnotations()));
                }

                Map<Class<? extends Annotation>, Annotation> map = new HashMap<Class<? extends Annotation>, Annotation>();

                for (Annotation a : annotations) {
                    map.put(a.annotationType(), a);
                }

                return map;
            }
        };
        private final CacheableValue<Object> entityInstance = new CacheableValue<Object>() {

            @Override
            protected Object calculate() {
                try {
                    return entityClass.newInstance();
                } catch (InstantiationException ex) {
                    throw new RuntimeException(ex);
                } catch (IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
            }
        };
        private CacheableValue<Constraint> constraint = new CacheableValue<Constraint>() {

            @Override
            protected Constraint calculate() {
                ConstraintBuilder builder = constraintBuilders.get(getBasicType());
                if (builder == null) {
                    throw new UnsupportedOperationException("Not possible create constraint for type " + getBasicType());
                }
                return builder.build(Property.this);
            }
        };

        public Property(Class entityClass, String propertyName) {
            this.propertyName = propertyName;
            this.entityClass = entityClass;
        }

        private Class getBasicType() {
            Class type = propertyDescriptor.getValue().getPropertyType();
            while (!(Object.class.equals(type) || Object.class.equals(type.getSuperclass()))) {
                type = type.getSuperclass();
            }
            return type;
        }

        private Constraint getConstraint() {
            return constraint.getValue();
        }
    }
}
